package com.smsc.headend.task.engine.executor.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.SystemClock;
import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONUtil;
import com.smsc.headend.common.enums.PaymentSwithPreToPostOptGroupEnum;
import com.smsc.headend.common.enums.errorcode.TaskExecuteErrorCode;
import com.smsc.headend.common.utils.RedisKeys;
import com.smsc.headend.common.utils.RedisUtils;
import com.smsc.headend.module.asset.dmo.ComConfigDMO;
import com.smsc.headend.module.asset.dmo.MeterConfigDMO;
import com.smsc.headend.module.asset.entity.ComEntity;
import com.smsc.headend.module.asset.entity.Meter;
import com.smsc.headend.module.common.entity.DeviceOperation;
import com.smsc.headend.module.data.dto.DataItemDTO;
import com.smsc.headend.module.data.enums.DictionaryType;
import com.smsc.headend.module.data.enums.OperationGroup;
import com.smsc.headend.module.data.enums.OperationName;
import com.smsc.headend.module.protocol.ProtocolType;
import com.smsc.headend.module.protocol.cosem.util.ByteUtils;
import com.smsc.headend.module.report.entity.LogModeSwitchHisSub;
import com.smsc.headend.module.task.dto.AtomicTask;
import com.smsc.headend.module.task.dto.AtomicTaskState;
import com.smsc.headend.module.task.dto.Task;
import com.smsc.headend.module.task.enums.SubTaskStatus;
import com.smsc.headend.module.task.enums.TaskType;
import com.smsc.headend.module.template.alarmfilter.entity.enums.AlarmFilterEnum;
import com.smsc.headend.task.engine.dao.TaskDAO;
import com.smsc.headend.task.engine.exception.UDISTaskExecutorException;
import com.smsc.headend.task.engine.executor.TaskExecutor;
import com.smsc.headend.task.engine.facade.DataItemFacade;
import com.smsc.headend.task.engine.handler.impl.ActionSuccessHandler;
import com.smsc.headend.task.engine.handler.impl.deviceconfig.SetAlarmHandler;
import com.smsc.headend.task.engine.handler.impl.deviceconfig.SetDisplayCodeHandler;
import com.smsc.headend.task.engine.handler.impl.metercontrol.SetControlModeHandler;
import com.smsc.headend.task.engine.handler.impl.whitelist.DeleteMeterListHandler;
import com.smsc.headend.task.engine.handler.impl.whitelist.SetMeterListHandler;
import com.smsc.headend.task.engine.service.ConnectorService;
import com.smsc.headend.task.engine.service.FeignAssetManagementService;
import com.smsc.headend.task.engine.service.asset.AssetService;
import com.smsc.headend.task.engine.service.task.TaskPacketService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
public class WriteExecutor implements TaskExecutor {
    @Autowired
    TaskPacketService packetService;
    @Autowired
    ConnectorService connectorService;
    @Autowired
    RedisUtils redisUtils;
    @Autowired
    FeignAssetManagementService assetManagementRemoteService;
    @Autowired
    DataItemFacade dataItemFacade;
    @Autowired
    TaskDAO taskDAO;
    @Autowired
    AssetService assetService;

    @Override
    public AtomicTaskState execute(Task task, AtomicTask atomicTask) throws UDISTaskExecutorException {
        DeviceOperation dictionary = assetService.getDictionary(atomicTask.getOperationId(), DictionaryType.Operation.getType());

        Meter meter = getMeter(task, assetService);
        MeterConfigDMO meterConfigDMO = task.getMeterId() == null ? null : assetService.getMeterConfigById(task.getMeterId());
        ComEntity terminal = assetService.getComById(task.getComId());
        ComConfigDMO comConfigDMO = assetService.getComConfigById(task.getComId());
        List<DataItemDTO> dataItemCosemList = dataItemFacade.getDataItemsByOperation(meter, terminal, dictionary, getParameterMap(task, atomicTask), comConfigDMO.getModelId());
        DeviceOperation operationGroup = assetService.getDictionary(atomicTask.getGroupId(), DictionaryType.OperationGroup.getType());

        byte[][] packets = getPacketByOperationGroup(task, atomicTask, meterConfigDMO, operationGroup, dataItemCosemList, meter, terminal);
        if (packets == null) {
            throw new UDISTaskExecutorException(TaskExecuteErrorCode.PACKETS_IS_NULL.toString());
        }
        log.info(ByteUtils.byteToHexString(packets[0]));
        connectorService.sendPacket(task, atomicTask, dataItemCosemList.get(0).getRoleId(), task.getIsODR(), packets);

        if (task.getTaskType().equals(TaskType.SwitchMeterPayment.toString())) {
            Map<String, Object> parameterMap = getParameterMap(task, atomicTask);
            Long taskId = MapUtil.getLong(parameterMap, "taskId");
            if (atomicTask.getGroupId() == OperationGroup.SwitchMeterPayment.getId().intValue()) {
                assetManagementRemoteService.updateStepStatus(taskId, task.getMeterId(), LogModeSwitchHisSub.Step.SwitchMeterPayment, SubTaskStatus.Processing.getCode(), SystemClock.now() / 1000, null, null);
            }
        }

        if (task.getTaskType().equals(TaskType.SwitchMeterPaymentPreToPost.toString())) {
            Map<String, Object> parameterMap = getParameterMap(task, atomicTask);
            Long taskId = MapUtil.getLong(parameterMap, "taskId");
            if (atomicTask.getGroupId() == OperationGroup.SwitchPaymentFromPreToPost.getId().intValue()) {
                assetManagementRemoteService.updateStepStatusToPost(taskId, task.getTaskNo(), task.getMeterId(), PaymentSwithPreToPostOptGroupEnum.SWITCH_PAYMENT_FROM_PRE_TO_POST.getOperationGroup().longValue(), SubTaskStatus.Processing.getCode(), SystemClock.now() / 1000, null, null, false);
            }
        }

        Class handlerClass = getHandlerClass(atomicTask);
        return createAtomicState(handlerClass);
    }

    @Override
    public Boolean handleFailure(Task task, AtomicTask atomicTask, String message) {
        log.info("error {}, ", message);
        if (task.getTaskType().equals(TaskType.SwitchMeterPayment.toString())) {
            Map<String, Object> parameterMap = getParameterMap(task, atomicTask);
            Long taskId = MapUtil.getLong(parameterMap, "taskId");
            Long now = SystemClock.now() / 1000;
            LogModeSwitchHisSub.Step step = null;
            switch (OperationGroup.forId(atomicTask.getGroupId().intValue())) {
                case ConnectArmed:
                    step = LogModeSwitchHisSub.Step.Connect;
                    break;
                case Disconnect:
                    step = LogModeSwitchHisSub.Step.Disconnect;
                    break;
                case SwitchMeterPayment:
                    step = LogModeSwitchHisSub.Step.SwitchMeterPayment;
                    break;
            }
            assetManagementRemoteService.updateStepStatus(taskId, task.getMeterId(), step, SubTaskStatus.Failed.getCode(), isSubTaskGroupFirstAtomicTask(task, atomicTask) ? now : null, now,
                    String.format("execute %s fail, [%s]", OperationName.forId(atomicTask.getOperationId()).getName(), message));
        } else if (task.getTaskType().equals(TaskType.SecuritySetup.toString())) {
            OperationName operationName = OperationName.forId(atomicTask.getOperationId());
            taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), operationName.getName(), message, 600L, false);
        } else if (task.getTaskType().equals(TaskType.SwitchMeterPaymentPreToPost.toString())) {
            Map<String, Object> parameterMap = getParameterMap(task, atomicTask);
            Long taskId = MapUtil.getLong(parameterMap, "taskId");
            if (atomicTask.getGroupId() == OperationGroup.SwitchPaymentFromPreToPost.getId().intValue()) {
                assetManagementRemoteService.updateStepStatusToPost(taskId, task.getTaskNo(), task.getMeterId(), PaymentSwithPreToPostOptGroupEnum.SWITCH_PAYMENT_FROM_PRE_TO_POST.getOperationGroup().longValue(), SubTaskStatus.Failed.getCode(), SystemClock.now() / 1000, SystemClock.now() / 1000, String.format("handle %s fail, [%s]", OperationName.forId(atomicTask.getOperationId()).getName(), message), false);
            }
        } else {
            String key = "message";
            if (atomicTask.getGroupId() == OperationGroup.AlarmFilterSet.getId().intValue()) {
                AlarmFilterEnum alarmFilterEnum = AlarmFilterEnum.forId(atomicTask.getSeq() + 1);
                String stepOperationName = (alarmFilterEnum == null) ? key : alarmFilterEnum.getName();
                taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), stepOperationName, String.format("[%s]", message), 3600l, false);
            } else if (atomicTask.getGroupId() == OperationGroup.ConnectArmed.getId().intValue()
                    || atomicTask.getGroupId() == OperationGroup.Connect.getId().intValue()
                    || atomicTask.getGroupId() == OperationGroup.Disconnect.getId().intValue()
                    || atomicTask.getGroupId() == OperationGroup.CheckCdc.getId().intValue()) {
                Map<String, Object> m = JSONUtil.toBean(task.getParamJson(), Map.class);
                key = m.get("seq") != null ? Convert.toStr(m.get("seq")) : key;
                String stepOperationName = OperationName.forId(atomicTask.getOperationId()).getName();
                taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), key, String.format("step %s failed, [%s]", stepOperationName, message), 3600l, false);
            }
        }
        return true;
    }

    private Class getHandlerClass(AtomicTask atomicTask) {
        Class cla = null;
        switch (OperationGroup.forId(atomicTask.getGroupId().intValue())) {
            case DCUSetMeterList:
                cla = SetMeterListHandler.class;
                break;
            case DCUDeleteMeterList:
                cla = DeleteMeterListHandler.class;
                break;
            case Connect:
            case ConnectArmed:
                cla = SetControlModeHandler.class;
                break;
            case AlarmFilterSet:
                cla = SetAlarmHandler.class;
                break;
            case SwitchMeterPayment:
            case SwitchPaymentFromPreToPost:
                cla = ActionSuccessHandler.class;
                break;
            case SetDisplay:
                cla = SetDisplayCodeHandler.class;
                break;
            default:
                break;
        }
        return cla;
    }

    private byte[][] getPacketByOperationGroup(Task task, AtomicTask atomicTask, MeterConfigDMO meterConfigDMO, DeviceOperation operationGroup, List<DataItemDTO> dataItemCosemList, Meter meter, ComEntity terminal) throws UDISTaskExecutorException {
        byte[][] packet = null;
        OperationGroup operationGp = OperationGroup.forId(operationGroup.getOptId().intValue());
        switch (operationGp) {
            case DCUSetMeterList:
                if (terminal == null) {
                    throw new UDISTaskExecutorException(TaskExecuteErrorCode.TERMINAL_NOT_EXISTS.toString());
                }
                ComConfigDMO comConfigDMO = assetService.getComConfigById(task.getComId());
                packet = writeDCUMeterListPacketByProtocol(terminal, task, dataItemCosemList, comConfigDMO);
                break;
            case DCUDeleteMeterList:
                if (terminal == null) {
                    throw new UDISTaskExecutorException(TaskExecuteErrorCode.TERMINAL_NOT_EXISTS.toString());
                }
                ComConfigDMO comConfigDMO1 = assetService.getComConfigById(task.getComId());
                packet = deleteDCUMeterListPacketByProtocol(terminal, task, dataItemCosemList, comConfigDMO1);
                break;
            case Connect:
            case ConnectArmed:
                packet = writeControlStatePacket(task, atomicTask, meterConfigDMO, meter, dataItemCosemList);
                break;
            case AlarmFilterSet:
                packet = setAlarmFilter(atomicTask, meterConfigDMO, meter, dataItemCosemList);
                break;
            case SwitchMeterPayment:
            case SwitchPaymentFromPreToPost:
                Map<String, Object> parameterMap = getParameterMap(task, atomicTask);
                String payment = MapUtil.getStr(parameterMap, "payment");
                packet = new byte[][]{packetService.writeSimpleDataPacket(dataItemCosemList, meter, Convert.toLong(payment), meterConfigDMO)};
                break;
            case SetDisplay:
                packet = writeDisplayPacketByProtocol(atomicTask, dataItemCosemList, meterConfigDMO);
                break;
            default:
                log.info("no implementation of this type {}", operationGp);
        }
        return packet;
    }

    private byte[][] writeDCUMeterListPacketByProtocol(ComEntity comEntity, Task task, List<DataItemDTO> dataItemCosemList, ComConfigDMO comConfigDMO) throws UDISTaskExecutorException {
        byte[][] packet;
        if (comConfigDMO.getProtocolId() == ProtocolType.DLMS_Gateway_Protocol.getProtocolId()) {
            Map<String, Object> map = JSONUtil.toBean(task.getParamJson(), Map.class);
            List<Long> meterIds = (List<Long>) ((List) map.get("meterIds")).stream().map(o -> Long.valueOf(String.valueOf(o))).collect(Collectors.toList());
            List<Meter> meters = meterIds.stream().map(meterId -> assetService.getMeterById(meterId)).collect(Collectors.toList());
            packet = packetService.writeDCUMeterList(comEntity, meters, dataItemCosemList, comConfigDMO);
        } else {
            throw new UDISTaskExecutorException(TaskExecuteErrorCode.PROTOCOL_NOT_EXISTS.toString());
        }
        return packet;
    }

    private byte[][] deleteDCUMeterListPacketByProtocol(ComEntity comEntity, Task task,
                                                        List<DataItemDTO> dataItemCosemList, ComConfigDMO comConfigDMO) throws UDISTaskExecutorException {
        byte[][] packet;
        comEntity.setProtocolId(ProtocolType.DLMS_Gateway_Protocol.getProtocolId());
        if (comConfigDMO.getProtocolId() == ProtocolType.DLMS_Gateway_Protocol.getProtocolId()) {
            Map<String, Object> map = JSONUtil.toBean(task.getParamJson(), Map.class);
            List<String> serialNums = (List<String>) map.get("serialNums");
            packet = packetService.deleteDCUMeterList(comEntity, serialNums, dataItemCosemList, comConfigDMO);
        } else {
            throw new UDISTaskExecutorException(TaskExecuteErrorCode.PROTOCOL_NOT_EXISTS.toString());
        }
        return packet;
    }

    private byte[][] writeControlStatePacket(Task task, AtomicTask atomicTask, MeterConfigDMO meterConfigDMO, Meter meter, List<DataItemDTO> dataItemCosemList) throws UDISTaskExecutorException {
        byte[][] packet = null;
        switch (OperationName.forId(atomicTask.getOperationId())) {
            case SetControlModeHardReconnect:
                int hardControlMode = 4;
                packet = new byte[][]{packetService.writeSimpleDataPacket(dataItemCosemList, meter, hardControlMode, meterConfigDMO)};
                break;
            case SetControlModeSoftReconnect:
                int softControlMode = 6;
                packet = new byte[][]{packetService.writeSimpleDataPacket(dataItemCosemList, meter, softControlMode, meterConfigDMO)};
                break;
            case SetControlModeOriginal:
                String hardControlModeKey = RedisKeys.getHardOriginalModeKey(task.getTaskNo());
                Integer originalControlMode = (Integer) redisUtils.hget(hardControlModeKey, "1");
                log.debug("ControlMode: " + originalControlMode);
                if (OperationGroup.ConnectArmed.getId().longValue() == atomicTask.getGroupId()) {
                    packet = new byte[][]{packetService.writeSimpleDataPacket(dataItemCosemList, meter, originalControlMode == null ? 4 : originalControlMode, meterConfigDMO)};
                }
                if (OperationGroup.Connect.getId().longValue() == atomicTask.getGroupId()) {
                    packet = new byte[][]{packetService.writeSimpleDataPacket(dataItemCosemList, meter, originalControlMode == null ? 6 : originalControlMode, meterConfigDMO)};
                }
                break;
        }
        return packet;
    }

    private byte[][] setAlarmFilter(AtomicTask atomicTask, MeterConfigDMO meterConfigDMO, Meter meter, List<DataItemDTO> dataItemCosemList) throws UDISTaskExecutorException {
        byte[][] packet = null;
        switch (OperationName.forId(atomicTask.getOperationId())) {
            case SetData:
                Map<String, Object> map = JSONUtil.toBean(atomicTask.getAtomicTaskParam(), Map.class);
                Long alarmFilterValue = Convert.toLong(map.get("alarmFilterValue"));
                packet = new byte[][]{packetService.writeSimpleDataPacket(dataItemCosemList, meter, alarmFilterValue, meterConfigDMO)};
                break;
        }
        return packet;
    }

    private byte[][] writeDisplayPacketByProtocol(AtomicTask atomicTask, List<DataItemDTO> dataItemCosemList, MeterConfigDMO meterConfigDMO) {
        Map<String, Object> map = JSONUtil.toBean(atomicTask.getAtomicTaskParam(), Map.class);
        List<Integer> codeList = (List<Integer>) ((List) map.get("codeList")).stream().map(o -> Integer.valueOf(String.valueOf(o))).collect(Collectors.toList());
        byte[][] packet = packetService.writeDisplayCodeList(codeList, dataItemCosemList, meterConfigDMO);
        return packet;
    }


}
